package a2;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

//**********************************************************
// Assignment2:
// UTOR user_name1: c3seagod
// UTOR user_name2: g3ctrliu
// UTOR user_name3: c3dibhil
// UTOR user_name4: c1oduoza
//
// Author1: Dylan Seago
// Author2: Tianran Liu
// Author3: Hilal Dib
// Author4: Paschal Oduoza
//
//
// Honor Code: I pledge that this program represents my own
//   program code and that I have coded on my own. I received 
//   help from no one in designing and debugging my program.
//   I have also read the plagiarism section in the course info
//   sheet of CSC 207 and understand the consequences.  
//*********************************************************
public class JShell {
	/**
	 * The {@link Directory} that the shell is currently working in.
	 */
	private Directory currentDir = FileSystem.getInstance().getRoot();

	/**
	 * 2D array of all valid commands split up into their terms. "-" represents
	 * a user defined input value, "/" represents a user defined Item path and
	 * "#" represents a valid command.
	 */
	private String[][] validCommands = new String[][] { { "exit" },
			{ "mkdir", "*" }, { "rm", "/" }, { "cd", "/" }, { "ls" },
			{ "ls", "*" }, { "pwd" }, { "mv", "/", "/" }, { "cp", "/", "/" },
			{ "cat", "/" }, { "echo", "-" }, { "echo", "-", ">>", "/" },
			{ "echo", "-", ">", "/" }, { "man", "#" },{"get", "*"}};

	/**
	 * Scanner Object used to get keyboard input.
	 */
	private Scanner in;

	/**
	 * Begins the command input loop which gets input from keyboard, checks if
	 * the command is valid then executes the command.
	 */
	public void run() {
		while (true) {
			String[] command = getCommand();
			// If the user entered an empty line
			if (command.length == 0 || command[0].equals("")) {
				continue;
			}
			try {
				if (validCommand(command)) {
					this.doCommand(command);
				} else
					// If command is not valid, error message is printed
					System.out.println("Invalid command, please try again");
			} catch (InvalidPathException e) {
				System.out.println(e);
			}
		}
	}

	/**
	 * Prints the current working directory followed by the command prompt (#).
	 * Gets input from the keyboard and splits the input at every space.
	 * 
	 * @return {@code String[]} of the keyboard input split at every space
	 */
	private String[] getCommand() {
		System.out.print(currentDir.getPath() + "# ");
		in = new Scanner(System.in);
		// Gets input from user
		String input = in.nextLine();

		// Removes leading and trailing white spaces
		input = input.trim();
		String inputCopy = input.replaceFirst("\"", "");
		if (input.contains("\"") && inputCopy.contains("\"")) {

			ArrayList<String> list = new ArrayList<String>();
			//Matcher m = Pattern.compile("([^\"]\\S*|\".+?\")\\s*").matcher(
					//input);
			Matcher m = Pattern.compile("([^\"]\\S*|\".+?\")\\s*").matcher(
					input);
			while (m.find())
				list.add(m.group(1));
			return list.toArray(new String[list.size()]);
		} else {
			// Splits input at any white spaces. The + accounts for multiple
			// consecutive spaces
			return input.replaceAll("\t", " ").split(" +");
		}
	}

	/**
	 * Takes a command input as an array of strings and iterates through a list
	 * of valid commands checking if it matches anything in the list. If there
	 * is a match true is returned.
	 * 
	 * @param command
	 *            Command input by user split into an array of strings
	 * @return {@code true} if the command is valid
	 */
	private boolean validCommand(String[] command) throws InvalidPathException {
		for (int i = 0; i < validCommands.length; i++) {
			if (command[0].equals(validCommands[i][0])) {
				for (int j = 0; j < validCommands[i].length; j++) {
					if (validCommands[i][j].equals("*"))
						return true;
					boolean validSyntax = false;
					// True if the command syntax matches this term.
					if (command.length - 1 >= j)
						validSyntax = command[j].equals(validCommands[i][j]);
					// True if command syntax allows for a user defined value
					boolean userValue = validCommands[i][j].equals("-");
					// True if command syntax allows for a user defined path
					boolean userPath = validCommands[i][j].equals("/");
					// True if command syntax allows for a user defined command
					boolean userCmd = validCommands[i][j].equals("#");
					/*
					 * Checks if # of params is equal to the # of params in the
					 * list of commands and that it is either matching syntax, a
					 * valid user defined path, or a user defined value.
					 */
					if ((command.length == validCommands[i].length)
							&& (validSyntax || userValue || userPath)
							|| userCmd) {
						// If command syntax allows for a user defined path and
						// the
						// path is invalid, throw InvalidPathException
						if (userPath) {
							if (!isValidPath(command[j]))
								throw new InvalidPathException(command[j]);
						}
						if (userCmd) {
							boolean valid = false;
							for (int k = 0; k < validCommands.length; k++) {
								if (command[j].equals(validCommands[k][0])) {
									valid = true;
									break;
								}
							}
							if (!valid)
								return false;
						}
						if (userValue) {
							if (!command[j].contains("\"")) {
								return false;
							}
							command[j] = command[j].replace("\"", "");
						}
						// If that is the last param to check, command is valid
						if (j == (command.length - 1))
							return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * Checks if the specified path is a valid path, but not whether the path
	 * exists.
	 * 
	 * @param path
	 *            Path to check
	 * @return {@code True} if the path is valid
	 */
	public static boolean isValidPath(String path) {
		// Check for empty path
		if (path.length() == 0)
			return false;

		// Checks for any consecutive slashes
		if (path.contains("//"))
			return false;

		return true;
	}

	/**
	 * Executes the specified command by calling the corresponding method
	 * 
	 * @param command
	 *            Command to execute
	 */
	private void doCommand(String[] command) {
		try {
			if (command[0].equals("exit")) {
				System.exit(0);
			} else if (command[0].equals("cd")) {
				this.cd(command[1]);
			} else if (command[0].equals("pwd")) {
				this.pwd();
			} else if (command[0].equals("mkdir")) {
				for (int x = 1; x < command.length; x++)
					FileSystem.getInstance().mkdir(currentDir, command[x]);
			} else if (command[0].equals("rm")) {
				// NEW in 2b: get confirmation
				System.out.print("Are you sure you want to delete \n"
						+ command[1] + " \n yes(y) OR no(n)");
				in = new Scanner(System.in);
				String input = in.nextLine();
				if (input.equals("y")) {
					FileSystem.getInstance().rm(currentDir, command[1]);
				} else
					System.out.print("OK, not delete.");
			} else if (command[0].equals("mv")) {
				FileSystem.getInstance().mv(currentDir, command[1], command[2]);
			} else if (command[0].equals("cp")) {
				FileSystem.getInstance().cp(currentDir, command[1], command[2]);
			} else if (command[0].equals("cat")) {
				Item item = FileSystem.getInstance().findItem(currentDir,
						command[1]);
				if (item instanceof File)
					System.out.println(((File) item).cat());
				else
					System.out
							.println("cat: " + command[1] + ": No such file.");
			} else if (command[0].equals("ls")) {
				if (command.length == 1) {
				
						System.out.println(currentDir.ls());
					
				} else
					for (int x = 1; x < command.length; x++) {
						
							System.out.print(FileSystem.getInstance()
								.findItem(currentDir, command[x]).ls());
						
					}
			} else if (command[0].equals("echo")) {
				if (command.length == 4)
					FileSystem.getInstance().echo(currentDir, command[1],
							command[3], command[2].equals(">") ? true : false);
				else
					FileSystem.getInstance().echo(currentDir, command[1]);
			} else if (command[0].equals("man")){
				this.man(command[1]);
			} else if (command[0].equals("get")){
				FileSystem.getInstance().get(currentDir,command[1]);
			}
		} catch (InvalidPathException e) {
			System.out.println(e.getMessage());
		} catch (PathNotFoundException e) {
			System.out.println(e.getMessage());
		}
	}

	/**
	 * Changes the current working {@link Directory} to the specified
	 * {@code path}.
	 * 
	 * @param path
	 *            Path name to change the current working directory to.
	 * @throws PathNotFoundException
	 *             Thrown when the path is not found.
	 */
	public void cd(String path) throws PathNotFoundException {
		Item item = FileSystem.getInstance().findItem(currentDir, path);
		if (item instanceof Directory)
			currentDir = (Directory) item;
		else
			System.out.println(path + ": Not a Directory");
	}

	/**
	 * Prints the current working directory to the console.
	 */
	public String pwd() {
		System.out.println(currentDir.getPath());
		return currentDir.getPath();
	}

	/**
	 * Prints out the documentation for the specified command.
	 * 
	 * @param command
	 *            Command to print documentation for.
	 */
	public void man(String command) {
		System.out.println("------Documentation for '" + command
				+ "' command------");
		Scanner input = null;
		try {
			input = new Scanner(new java.io.File("docs/" + command));
		} catch (FileNotFoundException e) {
			System.out.println("Nothing found");
			e.printStackTrace();
		}
		while (input.hasNextLine()) {
			System.out.println(input.nextLine());
		}
		input.close();
		System.out.println("------Documentation ends.------");
	}

	/**
	 * Main method. Initializes a new JShell and runs
	 * 
	 * @param args
	 *            Runtime arguments
	 */
	public static void main(String[] args) {
		JShell jShell = new JShell();
		jShell.run();
	}
}